using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.Windows;


//对象池需要使用的私有代码。需要挂载到对象池控制器的方法GetFromPool()内。
public interface IGetFromPool_self
{
    //需要生成的游戏物体，或者说预制体提供给GetFromPool的专属私有代码。
    public void Give_GetFromPool_self();

}


public class 坦克大战_对象池控制器 : MonoBehaviour
{

    //不懂这个序列化和反序列化的原理。但是如果没有这行代码，无法做到在inspector面板对类设置值。
    [System.Serializable]
    public class Pool_label
    { 
        /// <summary>
        /// 这是对对象池的数据定义的类，相当于给一堆对象池打标签。对象池本身是Queue，队列。后面会拿这个标签去找队列。
        /// 注意：这个类整体都是public。数值可以直接在inspector面板设置初始值。
        /// 一般来说，为了保证这个标签好使，对象池控制器这个脚本应该挂载到发射器上面。或者说，是负责生成预制体的物体上面。
        /// </summary>
        //对象池不止一个。给每个实例化的对象池一个名字。
        public string name;
        //正常来说，一个对象池只储存一种预制体。没有必要混乱储存。因为也能满足随机生成。
        public GameObject prefab;
        //对象池是一个队列
        public int length;
    }
    //一堆对象池标签。所有要用的对象池都留一个标签在这个列表里面。
    public List<Pool_label> liPool_label;
    //根据对象池标签搜索对象池的字典。string是对象池的名称的类型；Queue<GameObject>是对象池的类型，是个储存游戏物体的队列。
    Dictionary<string, Queue<GameObject>> dicPool_search;
    //单例模式。保证对象池控制器只有一个。避免出错。
    public static 坦克大战_对象池控制器 Instance;
    private void Awake()
    {
        Instance = this;

        //【注】最开始下面这些代码是放在Srart()里面的。老师指点，表示我在其他地方调用的时候，
        //pool_controller = 坦克大战_对象池控制器.Instance;是在Srart()里面调用的。这导致谁前谁后不清楚。如果先调用，对象池后填入值，
        //因为Srart()只执行一次，那么调用的地方所使用的对象池就缺少数据。
        //所以应该先让对象池填入值，然后再在使用的地方调用。一般调用是放在Srart()，所以对象池填入值应该放在Awake()或者OnEnable()这种在Srart()前面的方法中。
        //目前对象池填入值放在Awake()。好使，就这样。


        //游戏开始，先实例化字典。
        dicPool_search = new Dictionary<string, Queue<GameObject>>();
        //对每个对象池标签进行一次循环代码。
        foreach (Pool_label pool_Label in liPool_label)
        {
            //实例化一个储存游戏物体的队列。或者说，对象池。这就是对象池的实体。
            Queue<GameObject> cache_gameobject_pool = new Queue<GameObject>();
            //生成预制体游戏物体的实例，存入对象池队列。根据Pool_label的对象池长度数据决定充入数量，或者说执行的循环次数。根据Pool_label的对象池充入的预制体类型决定充入的游戏物体类型。
            for (int i = 0; i < pool_Label.length; i++)
            {
                //生成游戏物体。
                GameObject cache_gameobject = Instantiate(pool_Label.prefab);
                //生成马上隐藏。
                cache_gameobject.SetActive(false);
                //把游戏物体存入当前的对象池标签对应的对象池。
                cache_gameobject_pool.Enqueue(cache_gameobject);
            }
            //把当前对象池存入字典。给对象池用对象池标签上面的名称命名。就这样，通过对象池标签完整定义了一个对象池，并且关联对象池标签的名称和当前对象池。让对象池可以根据标签搜索出来。
            dicPool_search.Add(pool_Label.name, cache_gameobject_pool);
        }
    }
    


    /// <summary>
    /// 上述代码完整的把对象池生成过程定义了。
    /// 实际使用的时候：
    /// 1. 生成对象池。还需要对liPool_label存入标签。只有这样才会有对象池生成。目前是通过在inspector面板添加数据实现的。
    /// 2. 从对象池获取物体。调用还需要相应的调用代码。
    /// </summary>



    //从对象池获取对象。使用完回归队列的末位，实现重复使用。
    //有返回值的方法。返回值是游戏物体。
    //三个参数。poolname获取要调用的对象池；position是获取的物体初始位置；rotation是获取的物体初始方向。rotation是四元数，挺复杂的，直接获取物体的rotation传过来就行了。
    public GameObject GetFromPool( string poolname , UnityEngine.Vector3 position , UnityEngine.Quaternion rotation)
    {
        //如果不存在poolname对应的对象池。打印日志，返回空值。方法结束。UnityEngine.Quaternion rotation
        if ( !dicPool_search.ContainsKey(poolname) )
        {
            
            Debug.Log($"对象池：{poolname}不存在。");
            return null;
        }
        //如果存在poolname对应的对象池，继续运行代码。
        //从对象池队列的第一个位置开始出队，对该游戏物体赋予缓存名称，之后基于该名称对该游戏物体进行一系列设置。
        GameObject cache_gameobject_get = dicPool_search[poolname].Dequeue();
        //设置对象的初始位置和方向。
        cache_gameobject_get.transform.position = position;
        cache_gameobject_get.transform.rotation = rotation;
        //设置好初始位置方向之后再激活物体。
        cache_gameobject_get.SetActive(true);
        //这里是一个可以被置换的代码。专属代码。一般是预制体提供的。
        //【不确定】获取组件。可以看出，组件本质上就是接口。getFromPool_Self获取缓存游戏物体的组件，就是获取其中接口内的这些方法。
        IGetFromPool_self getFromPool_Self = cache_gameobject_get.GetComponent<IGetFromPool_self>();
        //并不是所有游戏物体都继承了该接口。对于没有继承的，就不打算在这里插入代码，所有直接不管就行了；如果继承了的，在这里执行代码。
        if ( getFromPool_Self != null )
        {
            //调用接口方法。
            getFromPool_Self.Give_GetFromPool_self();
        }
        //重新返回对象池队列。前面的同队列游戏物体使用完之后会再次被调用。
        //这里可能不好理解。对象池实际上相当于整个池子的对象都在游戏场景中的，只是有的被调用了或运动或静止，有的没有被调用是隐藏的。这些对象无论是什么状态，都是允许的。
        //重归对象池实际上是给游戏物体以编制、以标记，能够被重复初始化使用。和生成销毁无关。出对象池的目的是被缓存对象名cache_gameobject_get命名，进行一系列初始化操作。所以初始化完成马上回归。
        dicPool_search[poolname].Enqueue(cache_gameobject_get);
        //返回当前对象。供使用本方法的代码使用。
        return cache_gameobject_get;

    }











}





